<?php
/**
 * [WeEngine System] Copyright (c) 2013 WE7.CC
 * User: fanyk
 * Date: 2017/10/14
 * Time: 11:41
 */

namespace We7\Http;


use InvalidArgumentException;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;

class ServerRequest extends Request implements ServerRequestInterface {

	/**
	 * @var array
	 */
	private $attributes = array();

	/**
	 * @var array
	 */
	private $cookieParams = array();

	/**
	 * @var null|array|object
	 */
	private $parsedBody;

	/**
	 * @var array
	 */
	private $queryParams = array();

	/**
	 * @var array
	 */
	private $serverParams;

	/**
	 * @var array
	 */
	private $uploadedFiles = array();

	/**
	 * @param string                               $method       HTTP method
	 * @param string|UriInterface                  $uri          URI
	 * @param array                                $headers      Request headers
	 * @param string|null|resource|StreamInterface $body         Request body
	 * @param string                               $version      Protocol version
	 * @param array                                $serverParams Typically the $_SERVER superglobal
	 */
	public function __construct(
		$method,
		$uri,
		array $headers = array(),
		$body = null,
		$version = '1.1',
		array $serverParams = array()
	) {
		$this->serverParams = $serverParams;

		parent::__construct($method, $uri, $headers, $body, $version);
	}

	/**
	 * Return an UploadedFile instance array.
	 *
	 * @param array $files A array which respect $_FILES structure
	 * @throws InvalidArgumentException for unrecognized values
	 * @return array
	 */
	public static function normalizeFiles(array $files)
	{
		$normalized = array();

		foreach ($files as $key => $value) {
			if ($value instanceof UploadedFileInterface) {
				$normalized[$key] = $value;
			} elseif (is_array($value) && isset($value['tmp_name'])) {
				$normalized[$key] = self::createUploadedFileFromSpec($value);
			} elseif (is_array($value)) {
				$normalized[$key] = self::normalizeFiles($value);
				continue;
			} else {
				throw new InvalidArgumentException('Invalid value in files specification');
			}
		}

		return $normalized;
	}

	/**
	 * Create and return an UploadedFile instance from a $_FILES specification.
	 *
	 * If the specification represents an array of values, this method will
	 * delegate to normalizeNestedFileSpec() and return that return value.
	 *
	 * @param array $value $_FILES struct
	 * @return array|UploadedFileInterface
	 */
	private static function createUploadedFileFromSpec(array $value)
	{
		if (is_array($value['tmp_name'])) {
			return self::normalizeNestedFileSpec($value);
		}

		return new UploadedFile(
			$value['tmp_name'],
			(int) $value['size'],
			(int) $value['error'],
			$value['name'],
			$value['type']
		);
	}

	/**
	 * Normalize an array of file specifications.
	 *
	 * Loops through all nested files and returns a normalized array of
	 * UploadedFileInterface instances.
	 *
	 * @param array $files
	 * @return UploadedFileInterface[]
	 */
	private static function normalizeNestedFileSpec(array $files = array())
	{
		$normalizedFiles = array();

		foreach (array_keys($files['tmp_name']) as $key) {
			$spec = array(
				'tmp_name' => $files['tmp_name'][$key],
				'size'     => $files['size'][$key],
				'error'    => $files['error'][$key],
				'name'     => $files['name'][$key],
				'type'     => $files['type'][$key],
			);
			$normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
		}

		return $normalizedFiles;
	}

	/**
	 * Return a ServerRequest populated with superglobals:
	 * $_GET
	 * $_POST
	 * $_COOKIE
	 * $_FILES
	 * $_SERVER
	 *
	 * @return ServerRequestInterface
	 */
	public static function fromGlobals()
	{
		$method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
		$headers = function_exists('getallheaders') ? getallheaders() : array();
		$uri = self::getUriFromGlobals();
		$body = new Stream(fopen('php://input'));//new LazyOpenStream('php://input', 'r+');
		$protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';

		$serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);

		return $serverRequest
			->withCookieParams($_COOKIE)
			->withQueryParams($_GET)
			->withParsedBody($_POST)
			->withUploadedFiles(self::normalizeFiles($_FILES));
	}

	/**
	 * Get a Uri populated with values from $_SERVER.
	 *
	 * @return UriInterface
	 */
	public static function getUriFromGlobals() {
		$uri = new Uri('');

		$uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');

		$hasPort = false;
		if (isset($_SERVER['HTTP_HOST'])) {
			$hostHeaderParts = explode(':', $_SERVER['HTTP_HOST']);
			$uri = $uri->withHost($hostHeaderParts[0]);
			if (isset($hostHeaderParts[1])) {
				$hasPort = true;
				$uri = $uri->withPort($hostHeaderParts[1]);
			}
		} elseif (isset($_SERVER['SERVER_NAME'])) {
			$uri = $uri->withHost($_SERVER['SERVER_NAME']);
		} elseif (isset($_SERVER['SERVER_ADDR'])) {
			$uri = $uri->withHost($_SERVER['SERVER_ADDR']);
		}

		if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
			$uri = $uri->withPort($_SERVER['SERVER_PORT']);
		}

		$hasQuery = false;
		if (isset($_SERVER['REQUEST_URI'])) {
			$requestUriParts = explode('?', $_SERVER['REQUEST_URI']);
			$uri = $uri->withPath($requestUriParts[0]);
			if (isset($requestUriParts[1])) {
				$hasQuery = true;
				$uri = $uri->withQuery($requestUriParts[1]);
			}
		}

		if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
			$uri = $uri->withQuery($_SERVER['QUERY_STRING']);
		}

		return $uri;
	}


	/**
	 * {@inheritdoc}
	 */
	public function getServerParams()
	{
		return $this->serverParams;
	}

	/**
	 * {@inheritdoc}
	 */
	public function getUploadedFiles()
	{
		return $this->uploadedFiles;
	}

	/**
	 * {@inheritdoc}
	 */
	public function withUploadedFiles(array $uploadedFiles)
	{
		$new = clone $this;
		$new->uploadedFiles = $uploadedFiles;

		return $new;
	}

	/**
	 * {@inheritdoc}
	 */
	public function getCookieParams()
	{
		return $this->cookieParams;
	}

	/**
	 * {@inheritdoc}
	 */
	public function withCookieParams(array $cookies)
	{
		$new = clone $this;
		$new->cookieParams = $cookies;

		return $new;
	}

	/**
	 * {@inheritdoc}
	 */
	public function getQueryParams()
	{
		return $this->queryParams;
	}

	/**
	 * {@inheritdoc}
	 */
	public function withQueryParams(array $query)
	{
		$new = clone $this;
		$new->queryParams = $query;

		return $new;
	}

	/**
	 * {@inheritdoc}
	 */
	public function getParsedBody()
	{
		return $this->parsedBody;
	}

	/**
	 * {@inheritdoc}
	 */
	public function withParsedBody($data)
	{
		$new = clone $this;
		$new->parsedBody = $data;

		return $new;
	}

	/**
	 * {@inheritdoc}
	 */
	public function getAttributes()
	{
		return $this->attributes;
	}

	/**
	 * {@inheritdoc}
	 */
	public function getAttribute($attribute, $default = null)
	{
		if (false === array_key_exists($attribute, $this->attributes)) {
			return $default;
		}

		return $this->attributes[$attribute];
	}

	/**
	 * {@inheritdoc}
	 */
	public function withAttribute($attribute, $value)
	{
		$new = clone $this;
		$new->attributes[$attribute] = $value;

		return $new;
	}

	/**
	 * {@inheritdoc}
	 */
	public function withoutAttribute($attribute)
	{
		if (false === array_key_exists($attribute, $this->attributes)) {
			return $this;
		}

		$new = clone $this;
		unset($new->attributes[$attribute]);

		return $new;
	}
}